﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Gko.Utils
{
	internal class ElementComparer<TSource, TKey> : IElementComparer<TSource>
	{
		private readonly IComparer<TKey> comparer;
		private readonly bool isDescending;
		private readonly Func<TSource, TKey> keySelector;
		private readonly IElementComparer<TSource> next;
		private TKey[] keys;

		public ElementComparer(Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending, IElementComparer<TSource> next)
		{
			this.keySelector = keySelector;
			this.comparer = comparer;
			this.isDescending = descending;
			this.next = next;
		}

		#region IElementComparer<TSource> Members

		public void CreateSortKeys(TSource[] source)
		{
			// create the sort key from each item in the source array
			this.keys = new TKey[source.Length];
			for (var index = 0; index < source.Length; index++) {
				this.keys[index] = this.keySelector(source[index]);
			}

			// delegate to next if necessary
			if (this.next != null) {
				this.next.CreateSortKeys(source);
			}
		}

		public int Compare(int left, int right)
		{
			// invoke this level's comparer to get a basic result
			var result = this.comparer.Compare(this.keys[left], this.keys[right]);

			// if elements are different, return their relative order (inverting if descending)
			if (result != 0) {
				return this.isDescending ? -result : result;
			}

			// if there is a chained ordering, delegate to it
			if (this.next != null) {
				return this.next.Compare(left, right);
			}

			// elements are otherwise equal; to preserve stable sort, sort by original index
			return left - right;
		}

		public IElementComparer<TSource> Append(IElementComparer<TSource> elementComparer)
		{
			// append the new ordering to the tail of the current chain
			return this.next == null ? new ElementComparer<TSource, TKey>(this.keySelector, this.comparer, this.isDescending, elementComparer) : this.next.Append(elementComparer);
		}

		#endregion
	}
}
